package cn.demoncat.util.fastdfs;

import cn.demoncat.util.exception.BizErrorRuntimeException;
import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.fastdfs.constant.FdConstant.StorageCmd;
import cn.demoncat.util.fastdfs.constant.FdMsg;
import cn.demoncat.util.lang.StringUtil;
import cn.demoncat.util.lang.constant.StringConstant;
import org.apache.commons.lang3.StringUtils;
import org.csource.fastdfs.ProtoCommon;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;

/**
 * FastDFS连接：Storage Client
 * 
 * @author 延晓磊
 *
 * @since 2018年5月27日
 */
public class StorageClient {
	
	// Tracker Client
	private final TrackerClient trackerClient;
	
	/**
	 * 创建Storage Client
	 * 
	 * @param trackerClient
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年11月5日
	 */
	public StorageClient(TrackerClient trackerClient) {
		this.trackerClient = trackerClient;
	}
	
	/**
	 * 获取Tracker Client
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年11月5日
	 */
	public TrackerClient getTrackerClient() {
		return trackerClient;
	}
	
	/**
	 * 上传文件
	 * 
	 * @param group		分组
	 * @param ext		扩展名(不带.)
	 * @param file		文件
	 * 
	 * @return groupName/fileName.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public String[] upload(String group, String ext, byte[] file){
		// 获取Storeage Server
		StorageServer server = this.trackerClient.getUploadStorage(group);
		// 上传
		String[] fullFilename = this.upload(server, ext, file);
		// 关闭Storeage Server
		server.close();
		return fullFilename;
	}
 
	/**
	 * 上传文件(批量)
	 * 
	 * @param server	Storage Server（upload，失败时关闭）
	 * @param ext		扩展名(不带.)
	 * @param file		文件
	 * 
	 * @return groupName/fileName.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public String[] upload(StorageServer server, String ext, byte[] file){
		// 上传
		return this.upload(server, StorageCmd.STORAGE_PROTO_CMD_UPLOAD_FILE, null, null, ext, file);
	}
	
	/**
	 * 上传副本文件(批量)
	 * 
	 * @param server	Storage Server（update，失败时关闭）
	 * @param fileName	文件名
	 * @param slave		副本标记
	 * @param ext		扩展名(不带.)
	 * @param file		副本文件
	 * 
	 * @return groupName/fileName-slave.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public String[] uploadSlave(StorageServer server, String fileName, String slave, String ext, byte[] file) {
		return this.upload(server, StorageCmd.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, fileName, slave, ext, file);
	}
	
	/**
	 * 上传副本文件
	 * 
	 * @param group		分组
	 * @param fileName	文件名
	 * @param slave		副本标记
	 * @param ext		扩展名(不带.)
	 * @param file		副本文件
	 * 
	 * @return groupName/fileName-slave.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public String[] uploadSlave(String group, String fileName, String slave, String ext, byte[] file) {
		// 获取Storeage Server
		StorageServer server = this.trackerClient.getUpdateStorage(group, fileName);
		// 上传
		String[] fullFilename = this.uploadSlave(server, fileName, slave, ext, file);
		// 关闭Storeage Server
		server.close();
		return fullFilename;
	}

	/**
	 * 上传副本文件
	 * 
	 * @param fullFileName	完整文件名
	 * @param slave		副本标记
	 * @param ext		扩展名(不带.)
	 * @param file		副本文件
	 * 
	 * @return groupName/fileName-slave.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public String[] uploadSlave(String fullFileName, String slave, String ext, byte[] file) {
		String[] splitFileName = FdNameUtil.splitFileName(fullFileName);
		return this.uploadSlave(splitFileName[0], splitFileName[1], slave, ext, file);
	}
	
	/**
	 * 上传可续传/截断的文件
	 * 
	 * @param group		分组
	 * @param ext		扩展名(不带.)
	 * @param file		文件
	 * 
	 * @return groupName/fileName.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public String[] uploadAppender(String group, String ext, byte[] file) {
		// 获取Storeage Server
		StorageServer server = this.trackerClient.getUploadStorage(group);
		// 上传
		String[] fullFilename = this.uploadAppender(server, ext, file);
		// 关闭Storeage Server
		server.close();
		return fullFilename;
	}

	/**
	 * 上传可续传/截断的文件
	 * 
	 * @param server	Storage Server（upload，失败时关闭）
	 * @param ext		扩展名(不带.)
	 * @param file		文件
	 * 
	 * @return groupName/fileName.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public String[] uploadAppender(StorageServer server, String ext, byte[] file) {
		return this.upload(server, StorageCmd.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE, null, null, ext, file);
	}

	/**
	 * 续传文件
	 * 
	 * @param server	Storage Server（update，失败时关闭）
	 * @param filename	文件名
	 * @param file		文件块
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public void append(StorageServer server, String filename, byte[] file) {
		// 请求数据 
		byte[] appenderFilenameBytes = StringUtil.getBytes(filename);
		long body_len = 2L * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE + appenderFilenameBytes.length + file.length;
		byte[] header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_APPEND_FILE, body_len, (byte) 0);
		byte[] wholePkg = new byte[(int) (header.length + body_len - file.length)];
		System.arraycopy(header, 0, wholePkg, 0, header.length);
		int offset = header.length;
		byte[] hexLenBytes = ProtoCommon.long2buff(filename.length());
		System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
		offset += hexLenBytes.length;
		hexLenBytes = ProtoCommon.long2buff(file.length);
		System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
		offset += hexLenBytes.length;
		System.arraycopy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.length);
		offset += appenderFilenameBytes.length;
		try {
			// 发送请求
			OutputStream out = server.getOutputStream();
			out.write(wholePkg);
			out.write(file);
			// 接收响应
			ProtoCommon.recvPackage(server.getInputStream(), ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
		} catch (IOException ex) {
			// 关闭Storage Server
			server.close();
			throw new BizErrorRuntimeException(FdMsg.APPEND_ERROR,ex);
		} 
	}

	/**
	 * 删除文件
	 * 
	 * @param fullFilename	分组/文件名
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public void delete(String fullFilename) {
		// 去除前缀/
		fullFilename = FdNameUtil.initFilePath(fullFilename);
		// 切割组和文件
		String[] splitFileName = FdNameUtil.splitFileName(fullFilename);
		// 删除
		this.delete(splitFileName[0], splitFileName[1]);
	}
	
	/**
	 * 删除文件
	 * 
	 * @param group		分组
	 * @param filename	文件名
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public void delete(String group, String filename) {
		// 获取Storeage Server
		StorageServer server = this.trackerClient.getUpdateStorage(group, filename);
		// 请求数据
		byte[] groupBytes = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
		byte[] bs = StringUtil.getBytes(group);
		byte[] filenameBytes = StringUtil.getBytes(filename);
		Arrays.fill(groupBytes, (byte) 0);
		int groupLen;
		if (bs.length <= groupBytes.length) {
			groupLen = bs.length;
		} else {
			groupLen = groupBytes.length;
		}
		System.arraycopy(bs, 0, groupBytes, 0, groupLen);
		byte[] header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_DELETE_FILE, (long)(groupBytes.length) + filenameBytes.length, (byte) 0);
		byte[] wholePkg = new byte[header.length + groupBytes.length + filenameBytes.length];
		System.arraycopy(header, 0, wholePkg, 0, header.length);
		System.arraycopy(groupBytes, 0, wholePkg, header.length, groupBytes.length);
		System.arraycopy(filenameBytes, 0, wholePkg, header.length + groupBytes.length, filenameBytes.length);
		try {
			// 发送请求
			server.getOutputStream().write(wholePkg);
			// 接收响应
			ProtoCommon.recvPackage(server.getInputStream(),ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
		} catch (Exception ex) {
			throw new BizErrorRuntimeException(FdMsg.DELETE_ERROR,ex);
		} finally {
			// 关闭Storage Server
			server.close();
		}
	}
  
	/**
	 * 文件截断（记录原上传size > 截断 > 续传）
	 * 
	 * @param server	Storage Server（update，失败时关闭）
	 * @param filename	文件名
	 * @param size		截断大小（剩余尺寸）
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public void truncate(StorageServer server, String filename, long size){
		// 请求数据
		byte[] appenderFilenameBytes = StringUtil.getBytes(filename);
		int body_len = 2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE + appenderFilenameBytes.length;
		byte[] header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_TRUNCATE_FILE, body_len, (byte) 0);
		byte[] wholePkg = new byte[header.length + body_len];
		System.arraycopy(header, 0, wholePkg, 0, header.length);
		int offset = header.length;
		byte[] hexLenBytes = ProtoCommon.long2buff(filename.length());
		System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
		offset += hexLenBytes.length;
		hexLenBytes = ProtoCommon.long2buff(size);
		System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
		offset += hexLenBytes.length;
		System.arraycopy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.length);
		offset += appenderFilenameBytes.length;
		try {
			// 发送请求
			server.getOutputStream().write(wholePkg);
			// 接收响应
			ProtoCommon.recvPackage(server.getInputStream(), ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
		} catch (Exception ex) {
			// 关闭Storage Server
			server.close();
			throw new BizErrorRuntimeException(FdMsg.TRUNC_ERROR,ex);
		}
	}
	
	/**
	 * 下载文件
	 * 
	 * @param fullFilename	分组/文件名
	 * @param out			输出流（未关闭）
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public void download(String fullFilename, OutputStream out){
		// 去除文件前缀
		fullFilename = FdNameUtil.initFilePath(fullFilename);
		// 切割组和文件
		String[] gf = FdNameUtil.splitFileName(fullFilename);
		// 下载
		this.download(gf[0], gf[1], out);
	}
	
	/**
	 * 下载文件
	 * 
	 * @param group		分组
	 * @param filename	文件名
	 * @param out		输出流（未关闭）
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public void download(String group, String filename, OutputStream out) {
		// 获取Storage Server
		StorageServer server = this.trackerClient.getDownloadStorage(group, filename);
		// 请求数据
		byte[] bsOffset = ProtoCommon.long2buff(0);
		byte[] bsDownBytes = ProtoCommon.long2buff(0);
		byte[] groupBytes = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
		byte[] bs = StringUtil.getBytes(group);
		byte[] filenameBytes = StringUtil.getBytes(filename);
		Arrays.fill(groupBytes, (byte) 0);
		int groupLen;
		if (bs.length <= groupBytes.length) {
			groupLen = bs.length;
		} else {
			groupLen = groupBytes.length;
		}
		System.arraycopy(bs, 0, groupBytes, 0, groupLen);
		byte[] header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_DOWNLOAD_FILE, (long)(bsOffset.length) + bsDownBytes.length + groupBytes.length + filenameBytes.length, (byte) 0);
		byte[] wholePkg = new byte[header.length + bsOffset.length + bsDownBytes.length + groupBytes.length + filenameBytes.length];
		System.arraycopy(header, 0, wholePkg, 0, header.length);
		System.arraycopy(bsOffset, 0, wholePkg, header.length, bsOffset.length);
		System.arraycopy(bsDownBytes, 0, wholePkg, header.length + bsOffset.length, bsDownBytes.length);
		System.arraycopy(groupBytes, 0, wholePkg, header.length + bsOffset.length + bsDownBytes.length, groupBytes.length);
		System.arraycopy(filenameBytes, 0, wholePkg, header.length + bsOffset.length + bsDownBytes.length + groupBytes.length, filenameBytes.length);
		try {
			// 发送请求
			server.getOutputStream().write(wholePkg);
			// 读取响应
			InputStream in = server.getInputStream();
			// 接收响应
			long body_len = ProtoCommon.recvHeader(in, ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
			byte[] buff = new byte[2 * 1024];
			long remainBytes = body_len;
			int bytes;
			while (remainBytes > 0) {
				// 读到buff
				if ((bytes = in.read(buff, 0, remainBytes > buff.length ? buff.length : (int) remainBytes)) < 0) {
					throw new IOException( "recv package size " + (body_len - remainBytes) + " != " + body_len);
				}
				// 输出
				out.write(buff);
				remainBytes -= bytes;
			}
		} catch (Exception ex) {
			throw new BizErrorRuntimeException(FdMsg.DOWNLOAD_ERROR,ex);
		} finally {
			// 关闭Storage Server
			server.close();
		}
	}
	
	/**
	 * 上传文件：上传、副本、可续上传
	 * 
	 * @param server			Storage Server（upload/update，失败时关闭）
	 * @param cmd				命令（上传、副本、可续上传）
	 * @param filename			文件名（副本、可续上传不能为空）
	 * @param slave				副本标记（副本标识）
	 * @param ext				扩展名(不带.)
	 * @param file				文件
	 * 
	 * @return groupName/newFileName.ext 或 groupName/masterFileName-slave.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	private String[] upload(StorageServer server, StorageCmd cmd, String filename, String slave, String ext, byte[] file){
		// 是否为副本上传
		boolean isSlave = StringUtils.isNotBlank(slave);
		if (isSlave) {
			// -slave
			slave = StringConstant.BAR + slave;
		}
		
		// 扩展名
		byte[] ext_name_bs = new byte[ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN];
		Arrays.fill(ext_name_bs, (byte) 0);
		if (StringUtils.isNotBlank(ext)) {
			byte[] bs = StringUtil.getBytes(ext);
			int ext_name_len = bs.length;
			if (ext_name_len > ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN) {
				ext_name_len = ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN;
			}
			System.arraycopy(bs, 0, ext_name_bs, 0, ext_name_len);
		}
		
		// 请求数据
		int offset;
		long body_len;
		byte[] masterFilenameBytes;
		byte[] sizeBytes;
		byte[] hexLenBytes;
		if (isSlave) {
			masterFilenameBytes = StringUtil.getBytes(filename);
			sizeBytes = new byte[2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE];
			body_len = (long)(sizeBytes.length) + ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN + ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN + masterFilenameBytes.length + file.length;
			hexLenBytes = ProtoCommon.long2buff(filename.length());
			System.arraycopy(hexLenBytes, 0, sizeBytes, 0, hexLenBytes.length);
			offset = hexLenBytes.length;
		} else {
			masterFilenameBytes = null;
			sizeBytes = new byte[1 + ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE];
			body_len = (long)(sizeBytes.length) + ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN + file.length;
			sizeBytes[0] = (byte) server.getStorePathIndex();
			offset = 1;
		}
		hexLenBytes = ProtoCommon.long2buff(file.length);
		System.arraycopy(hexLenBytes, 0, sizeBytes, offset, hexLenBytes.length);
		byte[] header = ProtoCommon.packHeader(cmd.getCmd(), body_len, (byte) 0);
		byte[] wholePkg = new byte[(int) (header.length + body_len - file.length)];
		System.arraycopy(header, 0, wholePkg, 0, header.length);
		System.arraycopy(sizeBytes, 0, wholePkg, header.length, sizeBytes.length);
		offset = header.length + sizeBytes.length;
		
		if (isSlave) {
			byte[] prefix_name_bs = new byte[ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN];
			byte[] bs = StringUtil.getBytes(slave);
			int prefix_name_len = bs.length;
			Arrays.fill(prefix_name_bs, (byte) 0);
			if (prefix_name_len > ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN) {
				prefix_name_len = ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN;
			}
			if (prefix_name_len > 0) {
				System.arraycopy(bs, 0, prefix_name_bs, 0, prefix_name_len);
			}

			System.arraycopy(prefix_name_bs, 0, wholePkg, offset, prefix_name_bs.length);
			offset += prefix_name_bs.length;
		}
		System.arraycopy(ext_name_bs, 0, wholePkg, offset, ext_name_bs.length);
		offset += ext_name_bs.length;
		if (isSlave) {
			System.arraycopy(masterFilenameBytes, 0, wholePkg, offset, masterFilenameBytes.length);
			offset += masterFilenameBytes.length;
		}
		 
		try {
			// 发送请求
			OutputStream out = server.getOutputStream();
			out.write(wholePkg);
			out.write(file);
			
			// 接收响应
			byte[] recvPackage = ProtoCommon.recvPackage(server.getInputStream(), ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
			if (recvPackage.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
				throw new IoRuntimeException("body length: " + recvPackage.length + " <= " + ProtoCommon.FDFS_GROUP_NAME_MAX_LEN);
			}
			
			// 分组,文件名
			String new_group_name = new String(recvPackage, 0, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN).trim();
			String remote_filename = new String(recvPackage, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN, recvPackage.length - ProtoCommon.FDFS_GROUP_NAME_MAX_LEN);
			return new String[] {new_group_name,remote_filename};
		} catch (Exception ex) {
			// 关闭Storage Server
			server.close();
			throw new BizErrorRuntimeException(FdMsg.UPLOAD_ERROR, ex);
		}
	}
	
}
